package org.dreamwork.sso.saml2.protocol;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.dreamwork.sso.saml2.spec.IAuthEntity;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.util.AbstractConverter;
import org.dreamwork.util.IConverter;
import org.dreamwork.util.StringUtil;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.saml2.core.*;
import org.opensaml.saml2.core.impl.*;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.schema.XSAny;
import org.w3c.dom.Element;

import java.lang.reflect.Type;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: seth.yang
 * Date: 13-8-17
 * Time: 上午2:00
 */
public class SamlAssertion extends SamlObject<Assertion> {
    protected Assertion assertion;
    private SamlSubject subject;

    public Assertion getAssertion () {
        return assertion;
    }

    public SamlAssertion (XMLObjectBuilderFactory factory) {
        super (factory);

        assertion = new org.opensaml.saml2.core.impl.AssertionBuilder ().buildObject ();
    }

    public SamlAssertion (Assertion assertion) {
        super (Configuration.getBuilderFactory ());
        this.assertion = assertion;
    }

    @Override
    public Element getDom () {
        return assertion.getDOM ();
    }

    public String getId () {
        return assertion.getID ();
    }

    public DateTime getIssueInstant () {
        return assertion.getIssueInstant ();
    }

    public void setIssueInstant (DateTime issueInstance) {
        assertion.setIssueInstant (issueInstance);
    }

    public void setId (String id) {
        assertion.setID (id);
    }

    public String getIssuer () {
        Issuer issuer = assertion.getIssuer ();
        return issuer == null ? null : issuer.getValue ();
    }

    public void setIssuer (String issuer) {
        Issuer i = assertion.getIssuer ();
        if (i == null) {
            i = new IssuerBuilder ().buildObject ();
            assertion.setIssuer (i);
        }
        i.setValue (issuer);
    }

    public SamlSubject getSubject () {
        return subject;
    }

    public void setSubject (SamlSubject subject) {
        this.subject = subject;
        assertion.setSubject (subject.getSubject ());
    }

    public DateTime getNotOnOrAfter () {
        Conditions c = assertion.getConditions ();
        return c == null ? null : c.getNotOnOrAfter ();
    }

    public void setNotOnOrAfter (DateTime time) {
        Conditions c = assertion.getConditions ();
        if (c == null) {
            c = new ConditionsBuilder ().buildObject ();
            assertion.setConditions (c);
        }
        c.setNotOnOrAfter (time);
    }

    public void setNotOnOrAfter (int minutes) {
        DateTime now = new DateTime ();
        setNotOnOrAfter (now.plusMinutes (minutes));
    }

    public void setAuthnContext (String idp, String sessionId, int minutes) {
        List<AuthnStatement> list = assertion.getAuthnStatements ();
        AuthnStatement statement;
        if (list.size () == 0) {
            statement = new AuthnStatementBuilder ().buildObject ();
            list.add (statement);
        } else {
            statement = list.get (0);
        }

        DateTime now = new DateTime ();
        DateTime after = now.plusMinutes (minutes);
        statement.setAuthnInstant (now);
        statement.setSessionIndex (sessionId);
        statement.setSessionNotOnOrAfter (after);

        AuthnContext context = statement.getAuthnContext ();
        if (context == null) {
            context = new AuthnContextBuilder ().buildObject ();
            statement.setAuthnContext (context);
        }

        AuthnContextClassRef ref = context.getAuthnContextClassRef ();
        if (ref == null) {
            ref = new AuthnContextClassRefBuilder ().buildObject ();
            context.setAuthnContextClassRef (ref);
            ref.setAuthnContextClassRef ("urn:oasis:names:tc:SAML:2.0:ac:classes:Password");
        }

        List<AuthenticatingAuthority> as = context.getAuthenticatingAuthorities ();
        AuthenticatingAuthority authority;
        if (as.size () > 0) {
            authority = as.get (0);
        } else {
            authority = new AuthenticatingAuthorityBuilder ().buildObject ();
            as.add (authority);
        }
        authority.setURI (idp);
    }

    public void setAttributes (Object entity) {
        if (entity == null)
            throw new RuntimeException ("entity is null");

        List<AttributeStatement> list = assertion.getAttributeStatements ();
        list.clear ();

        AttributeStatement statement = new AttributeStatementBuilder ().buildObject ();
        list.add (statement);

        XMLObjectBuilder builder = factory.getBuilder (XSAny.TYPE_NAME);

        if (entity instanceof IAuthEntity) {
            setAttributes (statement, builder, (IAuthEntity) entity);
        } else {
            setAttributeAsJson (statement, builder, entity);
        }
    }

    @SuppressWarnings ("unchecked")
    public<T> T getEntity (Class<T> type) {
        Class<IAuthEntity> entityType = IAuthEntity.class;
        if (entityType.isAssignableFrom (type))
            return (T) getEntity ((Class<IAuthEntity>)type);

        return getEntityFromJson (type);
    }

    @SuppressWarnings ("unchecked")
    public<T> T getEntityFromJson (TypeToken<T> token) {
        Type type = token.getType ();
        return (T) GsonHelper.getGson ().fromJson (getJsonExpression (), type);
    }

    public<T> T getEntityFromJson (Class<T> type) {
        return GsonHelper.getGson ().fromJson (getJsonExpression (), type);
    }

    public<EntityType extends IAuthEntity> EntityType getEntity (Class<EntityType> type) {
        try {
            List<AttributeStatement> list = assertion.getAttributeStatements ();
            if (list == null || list.size () == 0) return null;
            AttributeStatement statement = list.get (0);
            List<Attribute> attributes = statement.getAttributes ();
            EntityType entity = type.newInstance ();

            Gson g = GsonHelper.getGson ();

            for (Attribute a : attributes) {
                String name = a.getFriendlyName ();
                if (StringUtil.isEmpty (name)) name = a.getName ();
                if (StringUtil.isEmpty (name)) {
                    logger.warn ("there's an attribute has no either FriendlyName nor Name, Ignore it");
                    continue;
                }
                Class<?> t = entity.getAttributeType (name);
                TypeToken<?> token = entity.getAttributeTypeToken (name);
                Object o;
                if (t != null) {
                    o = g.fromJson (getJsonExpression (a), t);
                } else if (token != null) {
                    o = g.fromJson (getJsonExpression (a), token.getType ());
                } else {
                    throw new RuntimeException ("Can't determine the type of entity attribute: " + name);
                }

                entity.setAttribute (name, o);
            }
            return entity;
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }

    private String getJsonExpression (Attribute attribute) {
        List<XMLObject> values = attribute.getAttributeValues ();
        if (values == null || values.size () == 0) return null;

        XSAny xml = (XSAny) values.get (0);
        return xml.getTextContent ();
    }

    private String getJsonExpression () {
        List<AttributeStatement> list = assertion.getAttributeStatements ();
        if (list == null || list.size () == 0) return null;

        AttributeStatement statement = list.get (0);
        List<Attribute> attributes = statement.getAttributes ();
        if (attributes == null || attributes.size () == 0) return null;

        Attribute attribute = attributes.get (0);
        String name = attribute.getName ();
        if (name == null || !name.equals ("entity"))
            throw new RuntimeException ("Can't find json expression");

        return getJsonExpression (attribute);
    }

    private void setAttributes (AttributeStatement statement, XMLObjectBuilder builder, IAuthEntity entity) {
        for (String name : entity.getAttributeNames ()) {
            Object value = entity.getAttribute (name);

            if (value == null) continue;

            Class type = value.getClass ();
            IConverter converter = AbstractConverter.getConverter (type);
            String stringValue = converter.cast (value);

            Attribute attribute = new AttributeBuilder ().buildObject ();
            attribute.setName (name);
            attribute.setFriendlyName (name);

            XSAny av = (XSAny) builder.buildObject (AttributeValue.DEFAULT_ELEMENT_NAME);
            av.setTextContent (stringValue);
            attribute.getAttributeValues ().add (av);

            statement.getAttributes ().add (attribute);
        }
    }

    private void setAttributeAsJson (AttributeStatement statement, XMLObjectBuilder builder, Object entity) {
        Gson g = GsonHelper.getGson ();
        Attribute attribute = new AttributeBuilder ().buildObject ();
        attribute.setFriendlyName ("entity");
        attribute.setName ("entity");

        XSAny av = (XSAny) builder.buildObject (AttributeValue.DEFAULT_ELEMENT_NAME);
        av.setTextContent (g.toJson (entity));
        attribute.getAttributeValues ().add (av);

        statement.getAttributes ().add (attribute);
    }
}